/** DEPENDENCY: PROTOTYPE 1.6+ */

	function nn(num){
		if(num<10) return "0"+num;
		else return ""+num;
	}

	var getSelectionHTML = function () 
	{
		
		try
		{
			var userSelection;
			if (window.getSelection) {
				// W3C Ranges
				userSelection = window.getSelection();
				// Get the range:
				if (userSelection.getRangeAt)
					var range = userSelection.getRangeAt(0);
				else {
					var range = document.createRange ();
					range.setStart (userSelection.anchorNode, userSelection.anchorOffset);
					range.setEnd (userSelection.focusNode, userSelection.focusOffset);
				}
				// And the HTML:
				var clonedSelection = range.cloneContents ();
				var div = document.createElement ('div');
				div.appendChild (clonedSelection);
				return div.innerHTML;
			} else if (document.selection) {
				// Explorer selection, return the HTML
				userSelection = document.selection.createRange ();
				return userSelection.htmlText;
			} else {
				return '';
			}
		}
		catch(e){
			// TODO log the error?
			return '';
		}
	
	};			
				
			


			
	function getMonthTableElement(calName,month,total_days)
	{
		var day=1;
		var mTable = document.createElement('table');
		Element.extend(mTable);
		mTable.cellPadding="0";
		mTable.cellSpacing="0";
		//mTable.border="1";
		mTable.addClassName("border");
		
			for(var i_tr=0; i_tr<5;i_tr++)
			{
				var mTr = document.createElement('tr');
				Element.extend(mTr);
				for(var i_td=0; i_td<7;i_td++)
				{
					var mTd = document.createElement('td');
					Element.extend(mTd);
					
						var mDay = document.createElement('div');
						Element.extend(mDay);

						if(day<=total_days)
						{
							mDay.addClassName("cell");
							mDay.id=calName+nn(month)+nn(day); // TODO nn
							mDay.innerHTML=day;
							day++;
						}else{
							mDay.addClassName("nondate");

						}
						Element.extend(mDay);
						mTd.appendChild(mDay);
					mTr.appendChild(mTd);
				}
				mTable.appendChild(mTr);
			}
		var calContainerSpan = document.createElement('span');	
		calContainerSpan.appendChild(mTable);
		return calContainerSpan;
	}	
	var daysInMonth = {
				1:31,
				2:29,
				3:31,
				4:30,
				5:31,
				6:30,
				7:31,
				8:31,
				9:30,
				10:31,
				11:30,
				12:31
			};
	var monthNames = {
				1:"January",
				2:"February",
				3:"March",
				4:"April",
				5:"May",
				6:"June",
				7:"July",
				8:"August",
				9:"September",
				10:"October",
				11:"November",
				12:"December"
			};
			

	var monthMap = {
			"01":"January",
			"02":"February",
			"03":"March",
			"04":"April",
			"05":"May",
			"06":"June",
			"07":"July",
			"08":"August",
			"09":"September",
			"10":"October",
			"11":"November",
			"12":"December"
		};

		var generateSerialCalendarTraversalArray = function(widgetName)
		{
			var daysInMonth = {
							1:31,
							2:29,
							3:31,
							4:30,
							5:31,
							6:30,
							7:31,
							8:31,
							9:30,
							10:31,
							11:30,
							12:31
						};
			var retArray = {}
			var i = 1;
			for(var month in daysInMonth){
				var numDays = daysInMonth[month];
				for(var day=1; day<= numDays; day++){
					var dateRaw = nn(month)+nn(day);
					var date = widgetName+dateRaw;
					retArray[i]=date;
					i++;
				}
			}
			return retArray;
		}
		
			
		var generateSerialCalendarLookupMap = function(widgetName)
		{
			var daysInMonth = {
							1:31,
							2:29,
							3:31,
							4:30,
							5:31,
							6:30,
							7:31,
							8:31,
							9:30,
							10:31,
							11:30,
							12:31
						};
			var retArray = {}
			var i = 1;
			for(var month in daysInMonth){
				var numDays = daysInMonth[month];
				for(var day=1; day<= numDays; day++){
					var dateRaw = nn(month)+nn(day);
					var date = widgetName+dateRaw;
					retArray[i]=date;
					retArray[date]=i;
					retArray[dateRaw]=i;

					i++;
				}
			}
			return retArray;
		}
	
	function printMap(map){
		var ret = "";
		for(var key in map){
			var val = map[key];
			ret += " "+key+":"+val;
		}
		return ret;
	}


	/* ++ Range names */
	setRangeName = function(dayDivElement, name) {
	    dayDivElement.writeAttribute('rangeName', name);
	}

	getRangeName = function(dayDivElement) {
	    return dayDivElement.readAttribute('rangeName');
	}

	clearRangeName = function(dayDivElement, name) {
	    dayDivElement.writeAttribute('rangeName');
	}
    /* -- Range names */


	function generateAgnosticCalendar(calName) {

        // Legend table
	    var lTable = document.createElement('table');
	    Element.extend(mTable);
	    lTable.cellPadding = "0";
	    lTable.cellSpacing = "0";
	    lTable.border = 0;

	    var lTr1 = document.createElement('tr');
	    Element.extend(lTr1);
	    lTable.appendChild(lTr1);
	    var lTd1_1 = document.createElement('td');
	    Element.extend(lTd1_1);
	    lTr1.appendChild(lTd1_1);
	    lTd1_1.colSpan = 3;
	    lTd1_1.align = 'center';

	    var div_legend_top = document.createElement('div');
	    div_legend_top.setAttribute("id", 'legend_top');
	    lTd1_1.appendChild(div_legend_top);

	    var lTr2 = document.createElement('tr');
	    Element.extend(lTr2);
	    lTable.appendChild(lTr2);

	    var lTd2_1 = document.createElement('td');
	    Element.extend(lTd2_1);
	    lTr2.appendChild(lTd2_1);
	    lTd2_1.setAttribute('valign', 'middle');

	    var div_legend_left = document.createElement('div');
	    div_legend_left.setAttribute("id", 'legend_left');
	    lTd2_1.appendChild(div_legend_left);

	    var lTd2_2 = document.createElement('td');
	    Element.extend(lTd2_2);
	    lTr2.appendChild(lTd2_2);
	    var lTd2_3 = document.createElement('td');
	    Element.extend(lTd2_3);
	    lTr2.appendChild(lTd2_3);

	    lTd2_3.setAttribute('valign', 'middle');

	    var div_legend_right = document.createElement('div');
	    div_legend_right.setAttribute("id", 'legend_right');
	    lTd2_3.appendChild(div_legend_right);

	    // Calendar itself table
		var current_month=1;
		var mTable = document.createElement('table');
		Element.extend(mTable);
		lTd2_2.appendChild(mTable); // linking calendar table into cell 2.2
		
		mTable.cellPadding="0";
		mTable.cellSpacing="0";
		
		for(var i_tr=0; i_tr<3;i_tr++)
		{
			var mTr = document.createElement('tr');
			Element.extend(mTr);
			for(var i_td=0; i_td<4;i_td++)
			{
				var mTd = document.createElement('td');
				Element.extend(mTd);
				
				// td contents:
				var monthname = document.createElement('div');
				Element.extend(monthname);
				monthname.addClassName("monthname");
				monthname.innerHTML=monthNames[current_month];
				mTd.appendChild(monthname);
				var month=current_month;
				var total_days=daysInMonth[current_month];
				current_month++;
				mTd.appendChild(getMonthTableElement(calName,month,total_days));
				
				mTr.appendChild(mTd);
			}
			mTable.appendChild(mTr);
		}

        // Last row of legend table
		var lTr3 = document.createElement('tr');
		Element.extend(lTr3);
		lTable.appendChild(lTr3);

		var lTd3_1 = document.createElement('td');
		Element.extend(lTd3_1);
		lTr3.appendChild(lTd3_1);
		lTd3_1.colSpan = 3;
		lTd3_1.align = 'center';

		var div_legend_bottom = document.createElement('div');
		div_legend_bottom.setAttribute("id", 'legend_bottom');
		lTd3_1.appendChild(div_legend_bottom);

		var calendarControl = document.createElement('div');
		Element.extend(calendarControl);
		var selectedDatesText = document.createElement('div');
		Element.extend(selectedDatesText);
		selectedDatesText.setAttribute("id", calName+"_selectedDatesText");
		selectedDatesText.addClassName("selectedDatesText");
		Element.extend(calendarControl);
		//TODO  allows text on top, bottom, left and right of the calendar
		//var wrapperTable = document.createElement('table');
		//Element.extend(wrapperTable);


		calendarControl.appendChild(lTable);
		calendarControl.appendChild(selectedDatesText);
		
		return calendarControl;
	}


    /** Converts a date range to a list of indexes */
    var dateRangeToIndexes = function(range, lookupMap){
    	
	    var startAndEndDates = range.split("-");
	    var startDate = startAndEndDates[0];
	    var endDate = startAndEndDates[1];
	    var startIndex = lookupMap[startDate];
	    var endIndex = lookupMap[endDate];
	    var retIndexes = {}
	    var ii=0;
	    // if endIndex is undefined, start index is the only day so only include it:
	    if(endIndex)
	    {		
		    for(var i=startIndex; i<=endIndex; i++){
			    retIndexes[ii]=i;
			    ii++;
		    }
	    }
	    else if(startIndex && startIndex != "")
	    {
		    retIndexes[ii]=startIndex;
	    }
	    return retIndexes;
    }


	    var Cursor = {
            pointer:function(event){
                //Event.element(event)  *** Prior to prototype 1.6 ***
                event.element().setStyle({cursor:"pointer"});
            },
        
            normal:function(event){
                event.element().setStyle({cursor:"default"});
            },
        
            auto:function(event){
                event.element().setStyle({cursor:"auto"});
            },
        
            crosshair:function(event){
                event.element().setStyle({cursor:"crosshair"});
            },
        
            move:function(event){
                event.element().setStyle({cursor:"move"});
            },
        
            e_resize:function(event){
                event.element().setStyle({cursor:"e-resize"});
            },
        
            ne_resize:function(event){
                event.element().setStyle({cursor:"ne-resize"});
            },
        
            n_resize:function(event){
                event.element().setStyle({cursor:"n-resize"});
            },
        
            se_resize:function(event){
                event.element().setStyle({cursor:"se-resize"});
            },
        
            sw_resize:function(event){
                event.element().setStyle({cursor:"sw-resize"});
            },
        
            s_resize:function(event){
                event.element().setStyle({cursor:"s-resize"});
            },
        
            w_resize:function(event){
                event.element().setStyle({cursor:"w-resize"});
            },
        
            text:function(event){
                event.element().setStyle({cursor:"text"});
            },
        
            wait:function(event){
                event.element().setStyle({cursor:"wait"});
            },
        
            help:function(event){
                event.element().setStyle({cursor:"help"});
            },
        
            progress:function(event){
                event.element().setStyle({cursor:"progress"});
            }
        }

		
	    disableAndHide = function(dayDivElement)
		{
			if(dayDivElement.hasClassName('cell')) 
				dayDivElement.removeClassName('cell');
				
			if(dayDivElement.hasClassName('cellavail')) 
				dayDivElement.removeClassName('cellavail');
			dayDivElement.addClassName('cellDisableddAndHidden');
		}
	    
		activate = function(dayDivElement)
		{
			if(dayDivElement.hasClassName('cell')) 
				dayDivElement.removeClassName('cell');
				
			if(!dayDivElement.hasClassName('cellavail')) 
				dayDivElement.addClassName('cellavail');
		}
			
		
		deactivate = function(dayDivElement)
		{
			if(dayDivElement.hasClassName('cellavail')) 
				dayDivElement.removeClassName('cellavail');
			if(!dayDivElement.hasClassName('cell')) 
				dayDivElement.addClassName('cell');
		}
		
		toggle = function(dayDivElement)
		{
			
			if(dayDivElement.hasClassName('cell')) 
			{
				activate(dayDivElement);
			}
			else 
			{
				deactivate(dayDivElement);
			}
		}

		var generateCtrlMap = function(widgetName, cal) {
		    var ret = new Array();
		    var i = 0;
		    for (var month in daysInMonth) {
		        var numDays = daysInMonth[month];
		        for (var day = 1; day <= numDays; day++) {
		            ret.push(cal.$(cal.lookupMap[i]));
		            i++;
		        }
		    }
		    return ret;
		}

		function AgnosticCalendar(calName) {
	        this.editEnabled = true;
		    this.calName = calName;
		    this.lookupMap = generateSerialCalendarLookupMap(calName);
		    this.traversalArray = generateSerialCalendarTraversalArray(calName);
		    this.lookupMapTextForDebug = printMap(this.lookupMap);
		    this.agc = generateAgnosticCalendar(calName);

		    this.dateToString = function(date){
			    var month = date.substring(0,2);
			    var day = date.substring(2,4);
			    var ret = monthMap[month]+" "+day;
			    return ret;
            }
        

        

		/**
		 * Convets an array of ranges to a user readable String 
		 * */
        this.rangesToStringCommaDelimited = function(ranges) {
            var ret = "";
            for (var i = 0; i < ranges.length;  i++) {
                var range = ranges[key];
                var r = range.split("-");
                var start = r[0];
                var end = null;
                if (r[1]) end = r[1];
                var retRange = "";
                start = start.substring(start.length - 4, start.length);
                retRange += this.dateToString(start);
                if (end) {
                    end = end.substring(end.length - 4, end.length);
                    retRange += "-" + this.dateToString(end);
                }
                if (ret != "") {
                    ret += ", " + retRange;
                } else {
                    ret += retRange;
                }
            }
            return ret;
        }

        // Converts range
        this.rangeToString = function(range) {
            var r = range.split("-");
            var start = r[0];
            var end = null;
            if (r[1]) end = r[1];
            var retRange = "";
            start = start.substring(start.length - 4, start.length);
            retRange += this.dateToString(start);
            if (end) {
                end = end.substring(end.length - 4, end.length);
                retRange += "-" + this.dateToString(end);
            }
            return retRange;
        }
		
		
		/**
		 * Convets an array of ranges to a user readable String 
		 * */
        this.rangesToString = function(ranges) {
            var ret = "";
            for (var key = 0; key < ranges.length; key++) {
                var range = ranges[key];
                var retRange = this.rangeToString(range);
                ret += "<div class='DateRangeText'>" + retRange + "</div>";
            }
            return ret;
        }
		
		/**
		 * Convets an array of ranges to a machine readable comma-delimited String 
		 * */
		this.rangesToMachineString = function(ranges)
		{	
			var ret="";
			for (var key = 0; key < ranges.length; key++) {
				var range = ranges[key];
				var retRange = this.rangeToString(range); 
				if (ret != "") {
					ret+= ","+retRange;
				}else{
					ret+= retRange;
				}
			}
			return ret;
		}
		
		this.toString = function(){
			return this.rangesToString(this.getSelectedRanges());
		}
		this.toMachineString = function(){
			return this.rangesToMachineString(this.getSelectedRanges());
		}
		this.onCalendarDatesChanged = function(ranges){}
		this.onCalendarDatesChangedByUserInput = function(ranges){}
		
		/** event fired when the calendar dates are changed*/
		this.fireCalendarDatesChangedEvent = function(ranges)
		{
			this.onCalendarDatesChanged(ranges);
		}
		
		/** event fired when the calendar dates are changed by user input*/
		this.fireCalendarDatesChangedEventByUserInput = function()
		{
			var ranges = this.toMachineString();
			this.onCalendarDatesChangedByUserInput(ranges);
		}
		this.refreshText = function(){
			var container = this.$(this.calName+"_selectedDatesText");
			container.innerHTML = this.toString()
				;/* debug: +"<p><b>"+this.toMachineString()+"</b></p>";*/
			this.fireCalendarDatesChangedEvent(this.toMachineString());
		}
	
		//alert(this.agc.getElementById('agc0101'));
		
		this.$ = function(dateElementId)
		{
			var cells = this.agc.getElementsByTagName("div"); 
			for (var i = 0; i < cells.length; i++) { 
				var id = cells[i].getAttribute("id"); 
				if ( id == dateElementId ) { 
					return cells[i];
				}
			}
		}

		this.controlMap = generateCtrlMap(calName, this);

		this.getSelectedIndexes = function()
		{
			var retArraySelectedIndexes = new Array();
			var ii = 0;
			var cells = this.agc.getElementsByTagName("div"); 
			for (var i = 0; i < cells.length; i++) { 
				var id = cells[i].getAttribute("id"); 
				iofId = (""+id).indexOf(this.calName);
				if ( iofId == 0 ) { 
					if(cells[i].hasClassName('cellavail'))
					{
						// then it is selected.
						retArraySelectedIndexes[ii] = this.lookupMap[id];
						ii++;
					}
				}
			}
			return retArraySelectedIndexes;
		}
		
		this.getUnSelectedIndexes = function()
		{
			var retArraySelectedIndexes = new Array();
			var ii = 0;
			var cells = this.agc.getElementsByTagName("div"); 
			for (var i = 0; i < cells.length; i++) { 
				var id = cells[i].getAttribute("id"); 
				iofId = (""+id).indexOf(this.calName);
				if ( iofId == 0 ) { 
					if(cells[i].hasClassName('cell'))
					{
						// then it is selected.
						retArraySelectedIndexes[ii] = this.lookupMap[id];
						ii++;
					}
				}
			}
			return retArraySelectedIndexes;
		}
		
		
		this.dateIndexesToRanges = function(arrayDateIndexes)
		{
		    // precondition, indexes must be sorted ahead of time.
		    // example: [1,2,3,5,6,233,234,235]
		    var retRanges = new Array();
		    var numRanges = 0;
		    var previous = arrayDateIndexes[0];
		    var currentFirstInRange = arrayDateIndexes[0];
		    var next; // the index
		    for (var ii = 1; ii <= arrayDateIndexes.length; ii++) {
		        next = arrayDateIndexes[ii];
		        var ctrlPrev = this.controlMap[previous];
		        var ctrlNext = this.controlMap[next];

		        if (previous + 1 == next &&
		            ctrlPrev.style.backgroundColor == ctrlNext.style.backgroundColor &&
		            getRangeName(ctrlPrev) === getRangeName(ctrlNext))
		        {
		            // this is part of an ongoing series
		        }
		        else {
		            if (currentFirstInRange == previous) {
		                // this is a "single range" - i.e. a single day
		                retRanges[numRanges] = this.lookupMap[currentFirstInRange];
		            } else {
		                retRanges[numRanges] = this.lookupMap[currentFirstInRange] + "-" + this.lookupMap[previous];
		            }
		            currentFirstInRange = next;
		            numRanges++;
		        }
		        previous = next;
		    }
		    // one last time for the last range:
		    if (next > currentFirstInRange) {
		        // the previous number was the last in a range a range
		        numRanges++;
		        retRanges[numRanges] = this.lookupMap[currentFirstInRange] + "-" + this.lookupMap[next];

		    }
		    return retRanges;
		}
		
		/**
			Input: an array of date ranges ranges (year agnostic) e.g. ['0101-0103','0302-1228']
			output: an array of the indexes of all the dates, lookued up using the lookupMap.
		*/
		this.dateRangesToIndexes = function(ranges)
		{
			var ret_Indexes = {};
			var i=0;
			for(var ri=0; ri<ranges.length; ri++)
			{
				var range = ranges[ri];
				var indexes = dateRangeToIndexes(range, this.lookupMap);
				for(var key_i in indexes){
					ret_Indexes[i] = indexes[key_i]; i++;
				}
			}
			return ret_Indexes;
		}
		/*
		 input: an array of ranges
		 * */
		this.paintRanges = function(ranges) {
		    if (this.editEnabled) {
		        if (!(ranges instanceof Array)) {
		            ranges = ranges.split(",");
		        }

		        var indexes = this.dateRangesToIndexes(ranges);
		        for (var key in indexes) {
		            var index = indexes[key];
		            var element = this.$(this.lookupMap[index]);
		            activate(element);

		        }
		    }
		}

		this.deactivateRanges = function(ranges) {
		    if (this.editEnabled) {
		        if (!(ranges instanceof Array)) {
		            ranges = ranges.split(",");
		        }

		        var indexes = this.dateRangesToIndexes(ranges);
		        for (var key in indexes) {
		            var index = indexes[key];
		            var element = this.$(this.lookupMap[index]);
		            deactivate(element);

		        }
		    }
		}
		
		this.clear = function()
		{
			this.deactivateRanges("0101-1231");
			
		}

		this.disableAndHideRanges = function(ranges) {
		    if (this.editEnabled) {
		        if (!(ranges instanceof Array)) {
		            ranges = ranges.split(",");
		        }

		        var indexes = this.dateRangesToIndexes(ranges);
		        for (var key in indexes) {
		            var index = indexes[key];
		            var element = this.$(this.lookupMap[index]);
		            disableAndHide(element);

		        }
		    }
		}

		this.getSelectedRanges = function()
		{
			var selected = this.getSelectedIndexes();
			var ranges = this.dateIndexesToRanges(selected);
			return ranges;
		}
		
		this.getUnSelectedRanges = function()
		{
			var unselected = this.getUnSelectedIndexes();
			var ranges = this.dateIndexesToRanges(unselected);
			return ranges;
		}
		
		//that=this;
		this.attachCalendarBehaviour = function(dayElement) {
		    Element.extend(dayElement);
		    dayElement.thisCalendar = this;
		    dayElement.observe('click', function(event) {
		        var element = event.element();
		        if (element.thisCalendar && element.thisCalendar.editEnabled) {
		            toggle(element);
		            // DEBUG: alert the selected ones:
		            var selected = element.thisCalendar.getSelectedIndexes();
		            var ranges = element.thisCalendar.dateIndexesToRanges(selected);
		            element.thisCalendar.refreshText();
		            element.thisCalendar.fireCalendarDatesChangedEventByUserInput();
		            //alert(printMap(ranges));
		        }
		    });

		    dayElement.observe("mouseover", function(event) {
		        Cursor.normal(event);
		        event.element().addClassName('cell-mouse-over');
		    });

		    dayElement.observe("mouseout", function(event) {
		        Cursor.normal(event);
		        event.element().removeClassName('cell-mouse-over');
		    });
		}
		
		
		for(var key in this.traversalArray)
		{
			var day = this.traversalArray[key];
			//for each day, toggle behaviour:
			this.attachCalendarBehaviour(this.$(day));
		}
		
	}

	function doOnSelect() {
	    
			var thisCalendar=null;	
			var selection = getSelectionHTML();
				if(selection.indexOf("agc")!=-1){

					for(var month in daysInMonth){
						var numDays = daysInMonth[month];
						for(var day=1; day<= numDays; day++){
							var date = "agc"+nn(month)+nn(day);
							if(selection.indexOf(date)!=-1){
							    var clickedDate = document.getElementById(date);
							    if (thisCalendar == null)
							        thisCalendar = clickedDate.thisCalendar;
							    if (thisCalendar.editEnabled) {
							        // toggle this date
							        toggle(clickedDate);
							    }
							}
						}
					}
					if (window.getSelection) window.getSelection().removeAllRanges();
					else if (document.selection) document.selection.empty();
					if (thisCalendar != null && thisCalendar.editEnabled) {
						thisCalendar.refreshText();
						thisCalendar.fireCalendarDatesChangedEventByUserInput();
					}
				}
			}
			document.onmouseup = doOnSelect;

		
		